本篇文章會完成 JWT 的設定,我們會利用在 User資料表中設定的 UserDetail 來幫忙將用戶資料設定到 JWT 中,生成的辦法會透過 Spring Security 內建的生成方式,Signature 的部分會隨機生成一個來當作加密密鑰,之後便可以檢查 JWT 中是否是使用相同密鑰來快速篩選。
先建立以下的 class
這裡我們會建立一些常用的方法讓其他地方可以調用,也就是將常用的功能寫成一個 class,讓需要使用的地方都可以使用
在這裡我們透過 @Bean 來將方法註冊到 Spring 的容器內,讓 Spring 幫忙管理這些方法的生命週期
@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
@Bean
public UserDetailsService userDetailsService(){
//使用userEmail搜尋對應的User資料
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
//獲取用戶詳情並驗證用戶憑證
authProvider.setUserDetailsService(userDetailsService());
//對用戶的密碼進行編碼和匹配
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
//處理身分驗證的主要接口
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
//將密碼進行加密
return new BCryptPasswordEncoder();
}
}
在這裡我們要進行 JWT 的生成的工作
首先設定過期時間和金鑰,金鑰的部分可以上網搜尋金鑰產生器,要注意的是如果密鑰太簡單是會報錯的
@Service
public class JwtService {
//設定過期時間為 1小時
private static final int EXPIRED_TIME = 60 * 60 * 1000;//過期時間,單位(ms)
//設定加密密碼
private static final String SECRET_EKY = "密鑰";
接著我們新增一個方法來幫助我們將用戶傳入的 token 裡面的資料給解析出來
private Claims extractAllClaims(String token){
// 獲取這個 token 的資料
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
然後新增幾個方法來獲取特定資料
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver){
// 獲取與這個 Token 相關的資料
final Claims claims = extractAllClaims(token);
// 傳回 Function 指定的資料,例如:Subject、Expiration
return claimsResolver.apply(claims);
}
private Date extractExpiration(String token) {
// 回傳這個 Token 的過期時間
return extractClaim(token, Claims::getExpiration);
}
public String extractUserName(String token) {
// 回傳這個 Token 的主體資料
return extractClaim(token, Claims::getSubject);
}
新增一個方法將我們的密鑰編碼成 Base64,並生成 Signature
private Key getSignInKey() {
// 生成簽名
byte[] keyBytes = Decoders.BASE64.decode(SECRET_EKY);
return Keys.hmacShaKeyFor(keyBytes);
}
接著新增驗證 Token 的方法,這個方法會使用在 JwtAuthenticationFillter 裡面,用來篩選 JWT 是否有問題
public boolean isTokenValid(String token, UserDetails userDetails){
// 檢查Token是否是有效的
// 先用傳進來的token獲取跟這個token相關的User名稱
final String userName = extractUserName(token);
//回傳 User 名稱是否相同,以及 Token 是否過期
return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
// 檢查這個 Token 的過期時間是不是在現在時間之前
return extractExpiration(token).before(new Date());
}
最後生成我們的 JWT,我們會使用 UserDetails 幫我們將資料整理好,我們再直接利用整理好的資料生成 JWT
public String generateToken(UserDetails userDetails){
// 外部程式呼叫這個方法並傳入使用者的相關資料
// HashMap不是必要的資料,如果要針對傳進來的資料進行一些額外標記的話
// 就可以在HashMap中放入對應的標記,讓生成JWT資料時將這個標記也加進JWT資料裡面
return buildToken(new HashMap<>(), userDetails);
}
public String buildToken(
Map<String,Object> extractClaims,
UserDetails userDetails )
{
return Jwts
.builder()
// 將特別的標記加進JWT資料
.setClaims(extractClaims)
// 將主體資料設定為User的名稱
.setSubject(userDetails.getUsername())
// 設定這個資料的建立時間
.setIssuedAt(new Date(System.currentTimeMillis()))
// 建立這個資料的過期時間
.setExpiration(new Date(System.currentTimeMillis() + EXPIRED_TIME))
// 使用指定的簽名算法和金鑰對 JWT 進行簽名
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
// 生成JWT資料
.compact();
}
由於後續的設定還很長,因此本篇文章先介紹到這裡,下篇文章會將後半的設定結束並且測試效果